import { trim } from './string'

/**
 * 某些方法来自jquery
 */
const class2type = {}
const toString = class2type.toString
const hasOwn = class2type.hasOwnProperty
const getProto = Object.getPrototypeOf
const fnToString = hasOwn.toString
const ObjectFunctionString = fnToString.call(Object)
// Populate the class2type map
// eslint-disable-next-line array-callback-return
'Boolean Number String Function Array Date RegExp Object Error Symbol'.split(' ').map((name) => {
  class2type[`[object ${name}]`] = name.toLowerCase()
})

/**
 * {string|*|string|'undefined'|'object'|'boolean'|'number'|'string'|'function'|'symbol'|'bigint'}
 */
/**
 * 获取对象类型，jquery
 */
function type(obj): any {
  if (obj == null) {
    return `${obj}`
  }
  // Support: Android <=2.3 only (function ish RegExp)
  return (typeof obj === 'object' || typeof obj === 'function') ? class2type[toString.call(obj)] || 'object' : typeof obj
}

/**
 * 是否是对象
 *  console.info( isPlainObject( { } ) ); // true
 *  console.info( isPlainObject( new Object() ) ); // true
 *  console.info( isPlainObject( { name: "CodePlayer"} ) ); // true
 *  console.info( isPlainObject( { sayHi: function(){} } ) ); // true
 *
 *  console.info(isPlainObject( "CodePlayer" ) ); // false
 *  console.info(isPlainObject( true ) ); // false
 *  console.info(isPlainObject( 12 ) ); // false
 *  console.info(isPlainObject( [ ] ) ); // false
 *  console.info(isPlainObject( function(){ } ) ); // false
 */
function isPlainObject(obj) {
  // Detect obvious negatives
  // Use toString instead of jQuery.type to catch host objects
  if (!obj || toString.call(obj) !== '[object Object]') {
    return false
  }
  const proto = getProto(obj)
  // Objects with no prototype (e.g., `Object.create( null )`) are plain
  if (!proto) {
    return true
  }
  // Objects with prototype are plain iff they were constructed by a global Object function
  const cstor = hasOwn.call(proto, 'constructor') && proto.constructor
  return typeof cstor === 'function' && fnToString.call(cstor) === ObjectFunctionString
}

/**
 * 是否是布尔型
 */
function isBoolean(a) {
  return type(a) === 'boolean'
}

/**
 * 是否是数字
 */
function isNumber(a) {
  return type(a) === 'number' && !Number.isNaN(a)
}

/**
 * 双问号运算符的 polyfill 时
 * 相当于 ES6 的 ??
 */
function nullValue(a, defVal) {
  if ((a !== null && a !== undefined)) {
    return a
  }
  return defVal
}

// .? 是可选链运算符
/**
 * // 使用示例
 * var obj = {
 *   foo: {
 *     bar: {
 *       baz: 42
 *     }
 *   }
 * };
 * console.log(optionalChainPolyfill(obj, 'foo', 'bar', 'baz'));  // 输出：42
 * console.log(optionalChainPolyfill(obj, 'foo', 'missing', 'baz'));  // 输出：undefined
 */
function optionalChain(obj, ...props) {
  let result = obj
  for (const prop of props) {
    if (result == null) {
      return undefined
    }
    result = result[prop]
  }
  return result
}

/**
 * 是否是字符串
 */
function isString(obj) {
  return type(obj) === 'string'
}

/**
 * 是否json字符串
 */
function isJSONString(str) {
  if (isString(str)) {
    try {
      const obj = JSON.parse(str)
      return isPlainObject(obj)
    } catch (_) {
      return false
    }
  }
  return false
}

/**
 * 是否是函数
 * @param obj
 */
function isFunction(obj) {
  return type(obj) === 'function'
}

/**
 * 是否为数组
 * @param obj
 */
function isArray(obj) {
  if (Array.isArray) {
    return Array.isArray(obj)
  }
  return type(obj) === 'array'
}

/**
 * 判断是否是日期类型
 * @param obj
 */
function isDate(obj) {
  return type(obj) === 'date'
}

/**
 * 是否是正则表达式
 * @param obj
 */
function isRegExp(obj) {
  return type(obj) === 'regexp'
}

/**
 * 是否是错误类型
 * @param obj
 */
function isError(obj) {
  return type(obj) === 'error'
}

/**
 * 是否是Symbol
 * @param obj
 */
function isSymbol(obj) {
  return type(obj) === 'symbol'
}

/**
 * 是否promise对象
 * @param obj
 */
export function isPromise(obj) {
  return isPlainObject(obj) && isFunction(obj.then) && isFunction(obj.catch)
}

/**
 * @description 深度克隆
 * @param {object} obj 需要深度克隆的对象
 * @returns {*} 克隆后的对象或者原值（不是对象）
 */
function deepClone(obj) {
  // 对常见的“非”值，直接返回原来值
  if ([null, undefined, Number.NaN, false].includes(obj))
    return obj
  if (typeof obj !== 'object' && typeof obj !== 'function') {
    // 原始类型直接返回
    return obj
  }
  const o = isArray(obj) ? [] : {}
  for (const i in obj) {
    if (Object.prototype.hasOwnProperty.call(obj, i)) {
      o[i] = typeof obj[i] === 'object' ? deepClone(obj[i]) : obj[i]
    }
  }
  return o
}

/**
 * @description JS对象深度合并
 * @param {object} target 需要拷贝的对象
 * @param objects 拷贝的来源对象
 * @param concat 为真对象中的数组会拼接，不会覆盖
 * @returns {*} 深度合并后的对象或者false（入参有不是对象）
 */
export function deepMerge(target = {}, objects, concat = false) {
  let sources = objects
  if (!isArray(objects)) {
    sources = [objects]
  }
  target = deepClone(target)
  if (typeof target !== 'object')
    return target
  for (const source of sources) {
    if (typeof source !== 'object') {
      return target
    }
  }
  for (const source of sources) {
    for (const prop in source) {
      if (!Object.prototype.hasOwnProperty.call(source, prop))
        continue
      if (prop in target) {
        if (typeof target[prop] !== 'object') {
          target[prop] = source[prop]
        } else if (typeof source[prop] !== 'object') {
          target[prop] = source[prop]
        } else if (target[prop].concat && source[prop].concat) {
          // 不要数组合并，这里注解了自会覆盖
          !concat && (target[prop] = source[prop])
          concat && (target[prop] = target[prop].concat(source[prop]))
        } else {
          target[prop] = deepMerge(target[prop], source[prop])
        }
      } else {
        target[prop] = source[prop]
      }
    }
  }
  return target
}

/**
 * 是否是空对象，没有任何属性
 */
export function isEmptyObject(obj) {
  // eslint-disable-next-line no-unreachable-loop
  for (const _ in obj) {
    return false
  }
  return true
}

/**
 *字符串判空
 */
export function isEmptyString(a) {
  const b = trim(a)
  if (type(a) === 'string' && b) {
    return b.length === 0
  }
  return true
}

/**
 *数组判空,数组是对象的一种
 */
export function isEmptyArray(a) {
  return !(isArray(a) && a.length > 0)
}

/**
 * 判断对象是否为空【不分类型】，数组length = 0 也是空， {}也是空
 */
export function isEmpty(obj) {
  if (Number.isNaN(obj) || obj === null || obj === undefined || obj === 0 || obj === false) {
    return true
  }
  if (type(obj) === 'string') {
    if (obj === 'undefined') {
      return true
    }
    return isEmptyString(obj)
  } else if (type(obj) === 'number') {
    return !isNumber(obj)
  } else if (isArray(obj)) {
    return isEmptyArray(obj)
  }
  // else if (type(obj) === 'object' && obj != null) {
  //   return isEmptyObject(obj)
  // }
  return isEmptyObject(obj)
}

/**
 * 判断对象不为空
 * @param obj
 */
export function isNotEmpty(obj) {
  return !isEmpty(obj)
}

export default {
  type,
  isPlainObject,
  isBoolean,
  isNumber,
  isString,
  isJSONString,
  isFunction,
  isArray,
  isDate,
  isRegExp,
  isError,
  isSymbol,
  isPromise,
  deepClone,
  deepMerge,
  isEmptyObject,
  isEmptyString,
  isEmptyArray,
  isEmpty,
  isNotEmpty,
  nullValue,
  optionalChain,
}
